/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * transfer.c : Transfer related routines
 * Copyright (C) 2009 Lingtao Pan
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA 02111-1307, USA.
 */

#include "common.h"

gftp_transfer *transfer;

enum {
    TRANS_ACTION_RESUME_ALL = 4,
    TRANS_ACTION_SKIP_ALL = 5,
    TRANS_ACTION_OVERWRITE_ALL = 6,
};


static int make_transfer_action_dialog (gftp_file *fle);
static int transfer_a_file (gftp_transfer *transfer, gftp_file *fle);
static void on_transfer_file_error (gftp_transfer *transfer, gftp_file *fle);

/* routines to query the state of transfer module */
int
transfer_get_state ()
{
    int state;

    if (transfer == NULL) {
        state = TRANSFER_NONE;
        return state;
    }

    g_static_mutex_lock (&transfer->structmutex);    
    if (transfer->started)
        state = TRANSFER_RUNNING;
    else if (transfer->trans_bytes + transfer->resumed_bytes 
             == transfer->total_bytes)
        state = TRANSFER_FINISHED;
    else  
        state = TRANSFER_STOPPED;

    g_static_mutex_unlock (&transfer->structmutex);    

    return state;
}


static void add_statistic (gftp_transfer *trans, gftp_file *fle, gboolean resume)
{
    if (! S_ISDIR (fle->st_mode)) {
        trans->numfiles++;
        trans->total_bytes += fle->size;
        if (resume)
            trans->resumed_bytes += fle->startsize;
    }
}

void *
transfer_append (gftp_transfer *trans_new)
{
  GList *ptr;
  gftp_file *fle;
  int ret;
  gboolean resume_all = FALSE, skip_all = FALSE, ovwr_all = FALSE;

  gftp_transfer_expand_dirs (trans_new);  


  /* ask the user whether existing files should be overwriten or resume. */
  for (ptr = trans_new->files; ptr; ptr=ptr->next) {
    fle = ptr->data;
    
    if (fle->exists_other_side == 0) {
        add_statistic (trans_new, fle, 0);
        continue;
    }

    if (resume_all) {
        fle->transfer_action = GFTP_TRANS_ACTION_RESUME;
        add_statistic (trans_new, fle, 1);
        continue;
    } else if (skip_all) {
        fle->transfer_action = GFTP_TRANS_ACTION_SKIP;
        continue;
    } else if (ovwr_all) {
        fle->transfer_action = GFTP_TRANS_ACTION_OVERWRITE;
        add_statistic (trans_new, fle, 0);
        continue;
    }

    gdk_threads_enter ();
    ret = make_transfer_action_dialog (fle);
    gdk_threads_leave ();

    switch (ret) {
    case GFTP_TRANS_ACTION_RESUME:
        fle->transfer_action = GFTP_TRANS_ACTION_RESUME;
        break;
    case GFTP_TRANS_ACTION_OVERWRITE:
        fle->transfer_action = GFTP_TRANS_ACTION_OVERWRITE;
        break;
    case GFTP_TRANS_ACTION_SKIP:
        fle->transfer_action =  GFTP_TRANS_ACTION_SKIP;
        break;
    case TRANS_ACTION_RESUME_ALL:
        fle->transfer_action = GFTP_TRANS_ACTION_RESUME;
        resume_all = TRUE;        
        add_statistic (trans_new, fle, 1);
        break;
    case TRANS_ACTION_SKIP_ALL:
        fle->transfer_action = GFTP_TRANS_ACTION_SKIP;
        skip_all = TRUE;
        break;
    case TRANS_ACTION_OVERWRITE_ALL:
        fle->transfer_action = GFTP_TRANS_ACTION_OVERWRITE;        
        ovwr_all = TRUE;
        add_statistic (trans_new, fle, 0);
        break;
    default:
        fle->transfer_action =  GFTP_TRANS_ACTION_SKIP;
        break;
    }
  }


  if (transfer == NULL)
      transfer = trans_new;
  else {
      gftp_transfer_merge (transfer, trans_new);
  }


  return NULL;
}



void* 
transfer_resume ()
{
    if (transfer == NULL)
        return NULL;
    return transfer_start ();
}

void *
transfer_start_from_beginning ()
{
  transfer->curfle = transfer->files; 

  return transfer_start (transfer);
}


void
transfer_clear ()
{
    GtkTreeStore *store = gftp_interface->dlstore;

    if (transfer == NULL)
        return;

    if (gftp_transfer_is_active (transfer))
        return;

    gtk_tree_store_clear (store);

    gftp_transfer_destroy (transfer);

    transfer = NULL;
}

void *
transfer_start ()
{
    gftp_file *fle;
    int ret, skipped_files;

    if (transfer == NULL)
        return NULL;

    if (transfer->curfle == NULL)
        transfer->curfle = transfer->files; 

    transfer->started = 1;

    gettimeofday (&transfer->starttime, NULL);
    memcpy (&transfer->lasttime, &transfer->starttime, 
            sizeof (transfer->lasttime));

    g_static_mutex_lock (&transfer->statmutex);
    transfer->accum_bytes = 0;
    transfer->average_kbs = 0;
    g_static_mutex_unlock (&transfer->statmutex);

    skipped_files = 0;

    g_assert (gftp_request_is_connected (transfer->fromreq));
    g_assert (gftp_request_is_connected (transfer->toreq));

    while (transfer->curfle != NULL) {
        fle = transfer->curfle->data;
        transfer->current_file_number++;
    
        if (fle->transfer_action == GFTP_TRANS_ACTION_SKIP) {
            transfer->curfsize = 0;
            transfer->curfle = transfer->curfle->next;
            continue;
        }

        if (S_ISDIR (fle->st_mode) || 
            (S_ISLNK(fle->st_mode) && fle->size == 0) ) 
        {
            if (!fle->exists_other_side) {
                ret = gftp_request_make_directory (transfer->toreq, fle->destfile);
        
                if (ret != 0) {
                    transfer->toreq->logging_function (gftp_logging_error, 
                                                       transfer->toreq,
                              _("Could not make direction %s in %s\n"),
                                                       fle->file, 
                                                       transfer->toreq);
        
                    break;
                }
            }
            transfer->curfsize = 0;
        } 
        else {
            ret = transfer_a_file (transfer, fle);

            if (ret < 0) {


                if (ret == GFTP_ETIMEDOUT) {
                    int i, retries, r;
                    retries = gftp_config_get_integer ("Network", "retries");
                    for (i = 0; i < retries; i++) {
                        if ((r = gftp_request_connect (transfer->fromreq)) == 0)
                            break;
                        if (r != GFTP_ETIMEDOUT)
                            goto err;
                    }

                    if (i != retries)
                        continue;
                }

                if (ret == GFTP_ERETRYABLE) {
                    /* skip current file and continue */
                       /* TODO: change the funcname*/
                    on_transfer_file_error (transfer, fle); 

                    fle->transfer_action = GFTP_TRANS_ACTION_SKIP;
                    transfer->curfle = transfer->curfle->next;
                    continue;
                }

                break;
            }
        }

        /* check if cancelled */
        g_static_mutex_lock (&transfer->structmutex);
        if (transfer->cancel) {
            transfer->stopped = 1;
            transfer->started = 0;
            transfer->cancel = 0;
            g_static_mutex_unlock (&transfer->structmutex);

            break;
        }
        g_static_mutex_unlock (&transfer->structmutex);

        transfer->curfle = transfer->curfle->next;
    }

    if (transfer->curfle == NULL) {
        transfer->done = 1;
    }

err:
    transfer->started = 0;

    g_static_mutex_lock (&transfer->statmutex);
    transfer->accum_bytes = 0;
    transfer->average_kbs = 0;
    g_static_mutex_unlock (&transfer->statmutex);
    
    return NULL;
}



static int
make_transfer_action_dialog (gftp_file *fle)
{
  GtkWidget *dialog, *label, *image, *hbox;
  char buf[1024];
  int result;

  dialog = gtk_dialog_new_with_buttons (_("Transfer Action"), 
                                   GTK_WINDOW (gftp_interface->window), 
                                   GTK_DIALOG_MODAL,
                                   _("Resume All"), TRANS_ACTION_RESUME_ALL,
                                   _("Overwrite ALL"), TRANS_ACTION_OVERWRITE_ALL,
                                   _("Skip All"), TRANS_ACTION_SKIP_ALL,
                                   _("Resume"), GFTP_TRANS_ACTION_RESUME,
                                   _("Overwrite"), GFTP_TRANS_ACTION_OVERWRITE,
                                   _("Skip"), GFTP_TRANS_ACTION_SKIP, 
                                   NULL);
  
  sprintf (buf, _("File %s already exists. Choose an transfer action."), 
           fle->file);
  label = gtk_label_new (buf);
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
  gtk_widget_set_size_request(label, 400, -1);
  image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO,
                                    GTK_ICON_SIZE_DIALOG);
  
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 10);
  gtk_box_pack_start_defaults (GTK_BOX (hbox), image);
  gtk_box_pack_start_defaults (GTK_BOX (hbox), label);

  gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG(dialog)->vbox), hbox);
  gtk_widget_show_all (dialog);

  result = gtk_dialog_run (GTK_DIALOG (dialog));


  gtk_widget_destroy (dialog);

  return result;
}

/*
static void
add_files_to_transfer (GList *files)
{
    GList *ptr;
    gftp_file *fle;

    for (ptr=files; ptr; ptr=ptr->next) {
        fle = gftp_file_copy (ptr->data);
        transfer->files = g_list_append (transfer->files, fle);
        dprintf ("transfer (after expanding dir) %s -> %s\n", 
                 fle->file, fle->destfile);
    }

    gdk_threads_enter ();
    transfer_view_set_transfer (transfer);
    gdk_threads_leave ();
}
*/


static ssize_t
_do_transfer_block (gftp_transfer * tdata, gftp_file * curfle, char *buf,
                    size_t trans_blksize)
{
    ssize_t num_read, num_wrote, ret;
    char *bufpos;

    num_read = gftp_request_get_next_file_chunk (tdata->fromreq, buf, 
                                                 trans_blksize);
    if (num_read < 0)
        return (num_read);

    bufpos = buf;
    num_wrote = 0;
    while (num_wrote < num_read)
    {
        if ((ret = gftp_request_put_next_file_chunk (tdata->toreq, bufpos,
                                           num_read - num_wrote)) <= 0)
            return (ret);

        num_wrote += ret;
        bufpos += ret;
    }

    return (num_read);
}



static int
transfer_a_file (gftp_transfer *transfer, gftp_file *fle)
{
    int ret;
    off_t size;
    char buf[8192];


    /* the following function is actually open the connection to transfer file */
    if (fle->exists_other_side) {
        if (fle->transfer_action == GFTP_TRANS_ACTION_OVERWRITE)
            size = gftp_transfer_transfer_file (transfer->fromreq, 
                                                fle->file, 0, transfer->toreq, 
                                                fle->destfile, 0);
        else
            size = gftp_transfer_transfer_file (transfer->fromreq, 
                                   fle->file, fle->startsize, transfer->toreq, 
                                   fle->destfile, fle->startsize);
    } else {
        size = gftp_transfer_transfer_file (transfer->fromreq, 
                                            fle->file, 0, transfer->toreq, 
                                            fle->destfile, 0);
    }

    if (size < 0) {
        fle->retry_transfer = 1;
        transfer->fromreq->logging_function (gftp_logging_error, transfer->fromreq,
                                        _("Could not download %s from %s\n"),
                                        fle->file, transfer->fromreq->hostname);
        return -1;
    }

    transfer->curfsize = fle->size;
    transfer->curtrans = 0;
    transfer->curresumed = fle->transfer_action == GFTP_TRANS_ACTION_RESUME ?
                                  fle->startsize : 0;
  

    /* actually transfer blocks */
    gettimeofday (&transfer->lasttime, NULL);
    while ( !transfer->cancel && ((ret = _do_transfer_block (transfer, fle, buf,
                                                             8192)) > 0) )
    {
        gftp_calc_kbs (transfer, ret);    
    }

    if (transfer->cancel) {
        fle->exists_other_side = 1;
        fle->transfer_action = GFTP_TRANS_ACTION_RESUME;
        fle->startsize = transfer->curtrans + transfer->curresumed;

        ret = gftp_request_abort_transfer (transfer->fromreq);
 
        if (ret < 0)
            gftp_request_disconnect (transfer->fromreq);
      
        ret = gftp_request_abort_transfer (transfer->toreq);
        if (ret < 0)
            gftp_request_disconnect (transfer->toreq);
      
        return 0;
    }

    if (ret == 0) {
        gftp_request_end_transfer (transfer->fromreq);
        gftp_request_end_transfer (transfer->toreq);

        transfer->fromreq->logging_function (gftp_logging_misc,
                                             transfer->fromreq,
                                _("Successfully transferred %s at %.2f KB/s\n"),
                                fle->file, transfer->kbs);

        fle->transfer_done = 1;
        transfer_view_update_item_upon_finish (fle);
    }


    return ret;
}


static void
on_transfer_file_error (gftp_transfer *transfer, gftp_file *fle)
{
    GtkTreePath *path;
    GtkTreeStore *store;
    GtkTreeIter iter;
    gtkui_file_pdata *uidata;

    transfer->toreq->logging_function (gftp_logging_error, 
                                       transfer->toreq,
                              _("Transfering file %s fails.\n"),
                                       fle->file);

     uidata = fle->user_data;

     /* TODO: if connection is down, abort */

     gdk_threads_enter (); 
  
     path = gtk_tree_row_reference_get_path (uidata->rowref);
     assert (path != NULL);
     store = GTK_TREE_STORE (gftp_interface->dlstore);
     gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);

     gtk_tree_store_set (store, &iter, COLUMN_DL_ACTION, _("Error"), -1);

     gdk_threads_leave ();
}
